#!/usr/bin/python
import Tkinter
import tkMessageBox
from VideoRecorder import VideoDownloader
from StreamViewer import StreamViewer
import time
from datetime import datetime
import threading
import Image, ImageTk
import StringIO
import traceback, sys               
from xml.dom.minidom import parse, parseString
import re, os
from JobsDb import JobsDb

def copyDictionary(d):
    d_new = dict()
    for k,v in d.iteritems():
        d_new[k] = v
    return d_new

def loadConfiguration(configPath, verbose = True):    
    d = parse(configPath)
    firstSp = re.compile('([^:]+:[0-9]+|[^/]+)(.*)')
    secSp = re.compile('([^:]+):([0-9]+)')
    variablePatt = '%d\[([0-9]+)\]'
    varRegex = re.compile(variablePatt)
    
    allConfig = list()
    allCameras = d.getElementsByTagName('cameras')
    for allC in allCameras:
        fatherName = allC.getAttribute('name').encode()
        fatherAddress = allC.getAttribute('address').encode()
        fatherUser = allC.getAttribute('username').encode()
        fatherPass = allC.getAttribute('password').encode()
        fatherPath = allC.getAttribute('saveto').encode()
        if len(fatherPath) == 0:
            fatherPath = 'MovieFrames'
        if (fatherAddress.lower().startswith('http://')):
            fatherAddress = fatherAddress[len('http://'):len(fatherAddress)]
        if len(fatherName) == 0:
            fatherName = 'Camera%d'
        if len(fatherAddress) == 0:
            fatherAddress = '127.0.0.1:%d[8080]'
            
        cameras = allC.getElementsByTagName('camera')
        rowI = 0
        for cam in cameras:
            rowI = rowI + 1
            camName = cam.getAttribute('name').encode()
            camAddress = cam.getAttribute('address').encode()
            camUser = cam.getAttribute('username').encode()
            camPass = cam.getAttribute('password').encode()
            savePath = cam.getAttribute('saveto').encode()
            if (camAddress.lower().startswith('http://')):
                camAddress = camAddress[len('http://'):len(camAddress)]            
            
            if len(savePath) == 0:
                matches = varRegex.findall(fatherPath)
                currV = fatherPath
                for m in matches:
                    currN = int(m)
                    currV = re.sub(variablePatt, str(currN + rowI -1), currV, 1)
                savePath = re.sub('%d', '%d'%rowI , currV)
            if not(cam.hasAttribute('username')):
                matches = varRegex.findall(fatherUser)
                currV = fatherUser
                for m in matches:
                    currN = int(m)
                    currV = re.sub(variablePatt, str(currN + rowI -1), currV, 1)
                camUser = re.sub('%d', '%d'%rowI , currV)
            if not(cam.hasAttribute('password')):
                matches = varRegex.findall(fatherPass)
                currV = fatherPass
                for m in matches:
                    currN = int(m)
                    currV = re.sub(variablePatt, str(currN + rowI -1), currV, 1)
                camPass = re.sub('%d', '%d'%rowI , currV)
            if len(camName) == 0:
                matches = varRegex.findall(fatherName)
                currV = fatherName
                for m in matches:
                    currN = int(m)
                    currV = re.sub(variablePatt, str(currN + rowI -1), currV, 1)
                camName = re.sub('%d', '%d'%rowI , currV)
                if verbose:
                    print 'WARN:camera "%s" is using default name'%camName
            if len(camAddress) == 0:
                matches = varRegex.findall(fatherAddress)
                currV = fatherAddress
                for m in matches:
                    currN = int(m)
                    currV = re.sub(variablePatt, str(currN + rowI -1), currV, 1)
                camAddress = re.sub('%d', '%d'%rowI , currV)
                if verbose:
                    print 'WARN:camera "%s" is using default address'%camName
                
            results = firstSp.findall(camAddress)
            if (len(results) != 1):
                raise NameError('Error1 Parsing address "%s"'%camAddress)
            res = results[0]
            if (len(res) != 2):
                raise NameError('Error2 Parsing address "%s"'%camAddress)
            firstAdd = res[0]
            requestPart = res[1]
            results = secSp.findall(firstAdd)
            if (len(results)== 0):
                hostName = firstAdd
                port = 80
            elif (len(results)== 1):
                res = results[0]
                if (len(res) != 2):
                    raise NameError('Parse error2 in internal Address "%s"'%firstAdd)
                hostName = res[0]
                port =  int(res[1])
            else:
                raise NameError('Parse error1 in internal Address "%s"'%firstAdd)

            if (len(requestPart) == 0):
                requestPart = '/'
            camConfig =  { 'ip': hostName,
                           'request': requestPart,
                           'port' : port,
                           'login': camUser,
                           'password' : camPass
                           }
            #print '"%s"'%requestPart
            camCfg = lambda:0
            camCfg.name = camName
            camCfg.config = camConfig
            camCfg.savepath = savePath
            allConfig.append(camCfg)
    return allConfig

class CameraModule(Tkinter.Frame):

     def __init__(self, parent, cameraName, config, savePath, frW = 400, frH = 300):
             Tkinter.Frame.__init__(self, parent)

             self.errorCount = 0
             slaveFrame = Tkinter.Frame(self)
             slaveFrame.pack()
             self.frameHeight = frH
             self.frameWidth = frW
             self.__updateFpsInterval = 1000
             self.__updateStreamInterval = 20
                
             self.cameraName = cameraName
             self.config = copyDictionary(config)
             self.savePath = savePath
             self.recorder = VideoDownloader(self.config, self.cameraName ,self.savePath)

             lblPath = Tkinter.Label(slaveFrame, text = "CameraName:")
             lblPath.pack(side = Tkinter.LEFT)

             textSavePath = Tkinter.Label(slaveFrame, text = cameraName)
             textSavePath.pack(side = Tkinter.RIGHT)

             self.streamViewer = StreamViewer(self, self.frameWidth, self.frameHeight) 

             speedText = Tkinter.StringVar()
             speedText.set('FPS: 0')
             streamSpeed = Tkinter.Label(self, textvariable = speedText) 
             streamSpeed.pack()

             frButtons = Tkinter.Frame(self)
             frButtons.pack()
             recordBtnText = Tkinter.StringVar()
             recordBtnText.set("Start Record")
             recordButton = Tkinter.Button(frButtons, textvariable = recordBtnText, command = self.RecordVideoClicked)
             recordButton.pack(side = Tkinter.RIGHT)

             watchBtnText = Tkinter.StringVar()
             watchBtnText.set("Start Watch")
             watchButton = Tkinter.Button(frButtons, textvariable = watchBtnText, command = self.WatchVideoClicked)
             watchButton.pack(side = Tkinter.LEFT)

             self.watchBtnText = watchBtnText
             self.recordButtonText = recordBtnText
             self.speedText = speedText

             #self.after(self.__updateFpsInterval, self.updateFps) # create update
             self.after(self.__updateStreamInterval, self.updateStreamGui) # create update

     def WatchVideoClicked(self):
         if (not(self.recorder.watchStatus())):
                  self.watchBtnText.set('Stop Watch')
                  self.recorder.onlyWatch()
                  self.after(self.__updateFpsInterval, self.updateFps) # create update
         else:
                  self.recordButtonText.set('Start Record')
                  self.watchBtnText.set('Start Watch')
                  self.recorder.stopWatch()

     def RecordVideoClicked(self):
             if (not(self.recorder.recordStatus())):
                  self.recordButtonText.set('Stop Record')
                  self.watchBtnText.set('Stop Watch')
                  self.recorder.startRecord()
                  self.after(self.__updateFpsInterval, self.updateFps) # create update
             else:
                  self.recordButtonText.set('Start Record')
                  self.recorder.stopRecord()

     def updateFps(self):
          self.speedText.set('FPS: %s'%self.recorder.getFrameSpeed())
          if (not(self.recorder.watchStatus())):
              sys.stderr.write('Camera "%s" Had Stoped!!\n'%self.cameraName)
              self.errorCount = self.errorCount + 1
              if (self.errorCount >= 1):
                  self.errorCount = 0
                  self.recordButtonText.set('Start Record')
                  self.watchBtnText.set('Start Watch')
                  self.speedText.set('FPS: 0')
                  self.recorder.stopWatch()
              else:
                  self.after(self.__updateFpsInterval, self.updateFps)
          else:
              self.after(self.__updateFpsInterval, self.updateFps)
        
     def updateStreamGui(self):
        try:
                lsImg = self.recorder.lastImg
                if (lsImg <> None):
                        imgS =Image.open(StringIO.StringIO(lsImg))
                        imgS = imgS.resize((self.frameWidth, self.frameHeight), Image.ANTIALIAS)
                        imagetk = ImageTk.PhotoImage(imgS) #Get a PhotoImage to pass to our Frame
                        self.streamViewer.addImage(imagetk) #Image added
                        self.update()
        except (IOError):
            sys.stderr.write('Coudn''t get frame in camera "%s"\n'%self.cameraName)
        except:
                traceback.print_exc()
                pass
        self.after(self.__updateStreamInterval, self.updateStreamGui)

        
        


class VideoGui:
         
    def __init__(self, configFile = 'Configuration.cfg'):
        mainWindow = Tkinter.Tk()
        mainWindow.title('Camera Streaming...')
        mainWindow.resizable(0,0)
        self.running = True
        mainWindow.protocol("WM_DELETE_WINDOW", self.CloseGui)
        self.__mainWindow = mainWindow 
        self.cameraFrame = list()
        self.closeRes = list()

        fTemp = Tkinter.Frame()
        screenWidth = fTemp.winfo_screenwidth() - 20 #window decoration overhead is 20 pixel
        screenHeight = fTemp.winfo_screenheight() - 100 #window decoration overhead is 100 pixel (include taskbar)

        configAll = loadConfiguration(configFile)
        
        camCount = len(configAll)
        rowNum = 1
        colNum = 1
        for i in xrange(2,camCount+1):
            if (i > rowNum*colNum): #out of grif
                if (colNum < rowNum):
                    colNum = colNum + 1
                else:
                    rowNum = rowNum + 1

        camH = int(screenHeight / rowNum) - 72 #cameraModule other widgets height overhead is 72
        camW = int(screenWidth / colNum) - 4 #cameraModule width overhead is 4

        for cfg in configAll:
            c = CameraModule(mainWindow, cfg.name, cfg.config, cfg.savepath, camW, camH)
            self.cameraFrame.append(c)
        
        i = 0
        rr = 1
        cc = 1
        for c in self.cameraFrame:
            cc = 1 + int(i / rowNum)
            rr =  1 + i % rowNum
            i = i + 1
            c.grid(row=rr, column =cc)

        if (camCount > 1):
            frAdd  = Tkinter.Frame(mainWindow)
            actionButtonAll = Tkinter.Button(frAdd, text = 'Start Watch All', command = lambda: self.SetWatchAll(True))
            actionButtonAll2 = Tkinter.Button(frAdd, text = 'Stop Watch All' , command = lambda: self.SetWatchAll(False))
            actionButtonAll3 = Tkinter.Button(frAdd, text = 'Start Record All' , command = lambda: self.SetRecordAll(True))
            actionButtonAll4 = Tkinter.Button(frAdd, text = 'Stop Record All' , command = lambda: self.SetRecordAll(False))
            actionButtonAll.pack(side = Tkinter.RIGHT)
            actionButtonAll2.pack(side = Tkinter.RIGHT)
            actionButtonAll3.pack(side = Tkinter.RIGHT)
            actionButtonAll4.pack(side = Tkinter.RIGHT)
            frAdd.grid(row = rowNum +1, column = 1)
            

    def SetWatchAll(self, flag):
        for c in self.cameraFrame:
            watchStat = c.recorder.watchStatus()
            if not(watchStat == flag):
                c.WatchVideoClicked()

    def SetRecordAll(self, flag):
        for c in self.cameraFrame:
            recordStat = c.recorder.recordStatus()
            if not(recordStat == flag):
                c.RecordVideoClicked()

    def addCloseResource(self, resource):
        self.closeRes.append(resource)
    
    def CloseGui(self):
        for cf in self.cameraFrame:
            if (cf.recorder.recordStatus()):
                cf.recorder.stopWatch()
        if (len(self.cameraFrame) >0):
            self.cameraFrame[0].recorder.exitAndClean()
        self.running = False
        for resource in self.closeRes:
            resource.Close()
        self.__mainWindow.destroy() 

    def RunGui(self):
        self.running = True
        self.__mainWindow.mainloop()
         

class jobCreator:
    def __init__(self,g, batchWait = 120):
        #self.cameras = g.cameraFrame
        self.waitTime = batchWait
        self.direCheck = dict()
        for c in g.cameraFrame:
            cameraSavePath = os.path.join(c.savePath, c.cameraName)
            self.direCheck[c.cameraName] = cameraSavePath
        self.pathState = dict()

        self.fullPathTime = dict()
        self.fullPathCount = dict()
        self.__frameNumRegex = re.compile('[0-9]+')
        self.db = JobsDb()
        self.running = True
        
    def createBatchJobs(self):
        dirsToCheck = set(self.direCheck.values())
        
        for r in dirsToCheck:
            if not(os.path.exists(r)):
                continue
            fileList = os.listdir(r)
            currTime = datetime.now()
            for ff in fileList:
                fullPath = os.path.join(r,ff)
                fullPath = os.path.abspath(fullPath);
                if not(os.path.isdir(fullPath)):
                    continue
                
                if not(self.pathState.has_key(r)):
                    self.pathState[r] = list()
                
                if not(fullPath in self.fullPathTime):
                    self.pathState[r].append(fullPath)
                    self.fullPathTime[fullPath] = datetime.now()
                    self.fullPathCount[fullPath] = 0
                else: # create batch
                    lastTime = self.fullPathTime[fullPath]
                    if (lastTime != None):
                        diffTime = (currTime - lastTime).total_seconds()
                        allFile = os.listdir(fullPath)
                    else:
                        diffTime = 0
                        
                    maxNum = -1
                    if (lastTime != None and diffTime > self.waitTime): #need to create batch!
                        self.fullPathTime[fullPath] = currTime
                        startJobFrom = self.fullPathCount[fullPath] + 1
                        
                        for a in allFile:
                            numT = self.__frameNumRegex.findall(a)
                            if(len(numT) > 0):
                                currNum = int(numT[0])
                                if (currNum > maxNum):
                                    maxNum = currNum
                        if (maxNum != -1):
                            endJob = maxNum
    
                    if (lastTime != None and ('video_info.txt' in allFile)):
                        self.fullPathTime[fullPath] = None
                        startJobFrom = self.fullPathCount[fullPath] + 1
                        for a in allFile:
                            numT = self.__frameNumRegex.findall(a)
                            if(len(numT) > 0):
                                currNum = int(numT[0])
                                if (currNum > maxNum):
                                    maxNum = currNum
                        if (maxNum != -1):
                            endJob = maxNum
                            
                    if (maxNum != -1): #create batch
                        self.fullPathCount[fullPath] = endJob
                        if (endJob >= startJobFrom):
                            self.db.InsertJob( fullPath,startJobFrom ,endJob)
                            print 'Job Created - ("%s", %d, %d)!'%(ff,startJobFrom,endJob)
                    
    def Close(self):
        self.running = False
    
    def runThread(self):
        checkTime = 3
        while(self.running):
            startTime = datetime.now()
            self.createBatchJobs()
            self.currTime = datetime.now()
            diffTime = (self.currTime - startTime).total_seconds()
            #print 'Check Duration: %s'%diffTime
            if (diffTime > checkTime):
                print 'WARN: too much time to run check loop: %s'%diffTime
                time.sleep(0.2)
            else:
                if (checkTime - diffTime > 0.2):
                    time.sleep(checkTime - diffTime)
                else:
                    time.sleep(0.2)
        print 'Exit Job Thread!'

if __name__ == "__main__":
    g = VideoGui()
    
    jobManger = jobCreator(g)
    g.addCloseResource(jobManger)
    jobThread = threading.Thread(target = jobManger.runThread)
    jobThread.isDaemon = True

    jobThread.start()
    g.RunGui()
    

